package ua.org.ass.html;

import java.io.*;
import java.util.*;

/**
 * Класс для роботи з HTML елементами.
 *
 * @author "Frick_ua"
 */
public class HTMLElement {

	/**
	 * Конструктор для створення пустого html-елементу з вказаним тегом.
	 *
	 * @param tag Тег html-елементу
	 */
	public HTMLElement(String tag) {
		this(tag, "", (Map) null);
	}

	/**
	 * Конструктор для створення html-елементу з вказаним тегом та вмістом.
	 *
	 * @param tag Тег html-елементу
	 * @param content Вміст html-елементу
	 */
	public HTMLElement(String tag, String content) {
		this(tag, content, (Map<String, String>) null);
	}

	/**
	 * Конструктор.
	 *
	 * @param tag Тег html-елементу
	 * @param attributes Атрибути html-елементу
	 * @param content Вміст html-елементу
	 */
	public HTMLElement(String tag, String content, String attributes) {
		this(tag, content);
		if (attributes != null && !attributes.isEmpty()) {
			String[] mas = attributes.split(" ");
			for (String m : mas) {
				String[] attr = m.split("=");
				this.attributes.put(attr[0], attr[1].replace("\"", ""));
			}
		}
	}

	public HTMLElement(String tag, String content, Map<String, String> attributes) {
		if (tag != null && !tag.isEmpty()) {
			this.tag = tag;
		} else {
			throw new IllegalArgumentException("TAG must be NOT NULL and NOT EMPTY!");
		}
		this.attributes = attributes != null
				  ? (HashMap<String, String>) attributes : new HashMap<String, String>();
		this.content = content;
		this.style = new HashMap<String, String>();
		this.childs = new LinkedHashMap<Integer, HTMLElement>();
		this.cssFiles = new ArrayList<String>();
		this.scriptFiles = new ArrayList<String>();
	}

	/**
	 * Додає дочірній елемент.
	 *
	 * @param element Елемент який необхідно додати.
	 */
	public void addChild(HTMLElement element) {
		if (tag.equalsIgnoreCase("span") && element.tag.equalsIgnoreCase("div")) {
			throw new IllegalArgumentException("Span element can not contain a div element.");
		}
		element.parent = this;
		childs.put(childs.size(), element);
	}

	public HTMLElement addChild(String tag) {
		return addChild(tag, "", "");
	}

	public HTMLElement addChild(String tag, String content) {
		return addChild(tag, content, "");
	}

	public HTMLElement addChild(String tag, String content, String attributes) {
		HTMLElement element = new HTMLElement(tag, content, attributes);
		addChild(element);
		return element;
	}
	
	public void addBr() {
		addChild("br");
	}
	
	/**
	 * Повертає строкове представлення всих дочірніх елементів.
	 *
	 * @return Строкове представлення всих дочірніх елементів
	 */
	private String getChilds() {
		StringBuilder ch = new StringBuilder();
		for (Map.Entry<Integer, HTMLElement> entry : childs.entrySet()) {
			HTMLElement htmlElement = entry.getValue();
			ch.append("");
			ch.append("   ");
			ch.append(htmlElement);
		}
		return ch.toString();
	}

	/**
	 * Повертає строкове представлення css-стилю елементу.
	 *
	 * @return Строкове представлення css-стилю елементу
	 */
	public String getStyle() {
		StringBuilder st = new StringBuilder();
		for (Map.Entry<String, String> entry : style.entrySet()) {
			String property = entry.getKey();
			String value = entry.getValue();
			st.append(property);
			st.append(": ");
			st.append(value);
			st.append(";");
		}
		return st.toString();
	}

	/**
	 * Повертає строкове представлення атрибутів елементу.
	 *
	 * @return Строкове представлення атрибутів елементу
	 */
	public Map<String, String> getAttributes() {
		return attributes;
	}

	/**
	 * Повертає значення вказаної властивості css-стилю елементу.
	 *
	 * @param property Властивість, значення якої необхідно повернути
	 * @return Значення вказаної властивості
	 */
	public String getStyleProperty(String property) {
		return style.get(property);
	}

	/**
	 * Додає властивість css-стилю елементу із вказаним значенням.
	 *
	 * @param property Властивість css-стилю
	 * @param value Значення властивості css-стилю
	 */
	public void addStyleProperty(String property, String value) {
		this.style.put(property, value);
	}

	/**
	 * Додає властивість css-стилю елементу за вказаним стилем.
	 *
	 * @param style css-стиль у форматі "властивість : значення"
	 */
	public void addStyleProperty(String style) {
		int i = style.indexOf(":");
		String property = style.substring(0, i).replaceAll(" ", "");
		String value = style.substring(i, style.length() - 1).replaceAll(
				  " ", "").replaceAll(":", "").replaceAll(";", "");
		addStyleProperty(property, value);
	}

	public void addCssFile(String url) {
		cssFiles.add(url);
	}

	public ArrayList<String> getCssFiles() {
		return cssFiles;
	}

	public void addCssFilesLinks(ArrayList<String> urls) {
		for (String url : urls) {
			addChild("link", "", "rel=\"stylesheet\" href=\"" + url +
					  "\" type=\"text/css\"");
		}
	}

	public void addScriptFile(String url) {
		scriptFiles.add(url);
	}

	public ArrayList<String> getScriptFiles() {
		return scriptFiles;
	}

	public void addScriptFilesLinks(ArrayList<String> urls) {
		for (String url : urls) {
			addChild("script", "", "src=\"" + url + "\"");
		}
	}


	/**
	 * Повертає строкове представлення елементу.
	 *
	 * @return Строкове представлення елементу
	 */
	@Override
	public String toString() {
		try {
			StringWriter sw = new StringWriter();
			readHTML(sw);
			return sw.toString();
		} catch (IOException ioe) {
			return super.toString();
		}
	}

	public void readHTML(Writer writer) throws IOException {
//		writer.append("   ");
		writer.append("<");
		writer.append(tag);

		for (Map.Entry<String, String> entry : attributes.entrySet()) {
			writer.append(" ");
			writer.append(entry.getKey());
			writer.append("=");
			writer.append("\"");
			writer.append(entry.getValue());
			writer.append("\"");
		}

		String stl = getStyle();
		if (stl != null && !stl.isEmpty()) {
			writer.append(" ");
			writer.append("style=\"");
			writer.append(stl);
			writer.append("\"");
		}

		if (tag.equalsIgnoreCase("HEAD")) {
			ArrayList<String> allScriptFiles = new ArrayList<String>(scriptFiles);
			Queue<HTMLElement> childss = new LinkedList<>(childs.values());

			while (childss != null && !childss.isEmpty()) {
				HTMLElement children = childss.poll();
				allScriptFiles.addAll(children.scriptFiles);
				if (!children.childs.isEmpty()) {
					childss.addAll(children.childs.values());
				}
			}
		}

		if (content == null || content.isEmpty() && childs.isEmpty() && !tag.equals("script")) {
			writer.append("/>");
		} else {
			writer.append(">");
			writer.append(content);
//			writer.append("   ");
			writer.append(getChilds());
			if (!tag.equalsIgnoreCase("br") && !tag.equalsIgnoreCase("img")) {
				writer.append("</");
				writer.append(tag);
				writer.append(">");
			}
		}
	}

	/**
	 * Повертає останній доданий дочірній елемент.
	 *
	 * @return Останній доданий дочірній елемент
	 */
	public HTMLElement getLastChild() {
		return childs.get(childs.size() - 1);
	}

	/**
	 * Повертає тег елементу.
	 *
	 * @return Тег елементу
	 */
	public String getTag() {
		return tag;
	}

	/**
	 * Встановлює тег елементу.
	 *
	 * @param tag Тег елементу
	 */
	public void setTag(String tag) {
		this.tag = tag;
	}

	/**
	 * Повертає вміст елементу.
	 *
	 * @return Вміст елементу
	 */
	public String getContent() {
		return content;
	}

	/**
	 * Встановлює вміст елементу.
	 *
	 * @param content Вміст елементу
	 */
	public void setContent(String content) {
		this.content = content;
	}

	public HTMLElement setAttribute(String name, String value) {
		attributes.put(name, value);
		return this;
	}

	public HTMLElement setId(String value) {
		attributes.put("id", value);
		return this;
	}

	public HTMLElement setClass(String value) {
		attributes.put("class", value);
		return this;
	}

	public HTMLElement setName(String value) {
		attributes.put("name", value);
		return this;
	}

	public HTMLElement setCharset(String value) {
		attributes.put("charset", value);
		return this;
	}

	public HTMLElement setContentAttr(String value) {
		attributes.put("content", value);
		return this;
	}

	public HTMLElement setSrc(String value) {
		attributes.put("id", value);
		return this;
	}

	public HTMLElement setRel(String value) {
		attributes.put("rel", value);
		return this;
	}

	public HTMLElement setHref(String value) {
		attributes.put("href", value);
		return this;
	}

	public HTMLElement setType(String value) {
		attributes.put("type", value);
		return this;
	}


	public HTMLElement setPlaceHolder(String value) {
		attributes.put("placeholder", value);
		return this;
	}

	public HTMLElement setOnClick(String value) {
		attributes.put("onclick", value);
		return this;
	}

	public HTMLElement setOnInput(String value) {
		attributes.put("oninput", value);
		return this;
	}
	
	/*	Тег html-елементу. */
	private String tag;
	/*	Вміст html-елементу. */
	private String content;

	/** Сss властивості html-елементу із значеннями. */
	private HashMap<String, String> style;

	/** Css файли необхідні для відображення елементу. */
	private ArrayList<String> cssFiles;

	/** JavaScript файли необхідні для роботи компоненту. */
	private ArrayList<String> scriptFiles;

	private HashMap<String, String> attributes;

	/*	Дочірні елементи до цього html-елементу. */
	private LinkedHashMap<Integer, HTMLElement> childs;

	private HTMLElement parent = null;
}
